package androidlab.serverclasses;

import java.io.BufferedReader;
import java.io.IOException;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.io.OutputStream;
import java.io.InputStream;


public class Server{
	
	private final static int N_THREADS = 10;
	private final static int PORT = 8000;
	private final static int TIMEOUT = 500; // accept() timeout in ms

	private static enum State {
		ONLINE, OFFLINE
	}
	
	
	private volatile State currentState = State.OFFLINE;

	private Thread tcpThread;
	private Thread udpThread;
	public ThreadPool threadPool;
	

	private boolean firstTimeUPD;
	private TypeConverter converter;
	//variables filled by data received from client by UPD
	//private double longitude, latitude, speed, time, futureMovement;
	//private int ID; 
	private double[] predictionVector;
	private HashMap<Integer,MobileUser> mobileUserMap;
	//protected Thread runningThread= null;
	//ServerSocket TCPServerSocket;
	//DatagramSocket UDPServerSocket;
	
	
	// Server must: Receive from a mobile user. Use a thread from the threadpool to serve that request.
	// Each thread must write their own gpx file. Use the ID of the mobile user as filename? 
	
	// TODO: Implement with threadpool.
	
	public Server(){
		
		//sizeOfThreadPool =nThreads;
		threadPool = new ThreadPool(N_THREADS);
		mobileUserMap = new HashMap<Integer,MobileUser>();
		firstTimeUPD = true;
		predictionVector = new double[2];

	}
	
	

	
	public void start(){
		
		
			System.out.println("Sever starting...");
			if (currentState == State.OFFLINE) {

				Runnable runTcp = new Runnable() {
					public void run() {
						runTcp();
						tcpThread = null;
					}
				};

				Runnable runUdp = new Runnable() {
					public void run() {
						runUdp();
						udpThread = null;
					}
				};

				tcpThread = new Thread(runTcp, getClass().getName());
				udpThread = new Thread(runUdp, getClass().getName());
				currentState = State.ONLINE;
				tcpThread.start();
				udpThread.start();
				
			}
			System.out.println("Server starting... DONE");
		}
	
	public void stop() {
		System.out.println("Server stopping...");
		if (currentState == State.ONLINE) {
			currentState = State.OFFLINE;
			threadPool.stopRequestAllWorkers();		
		}
		
		System.out.println("Server stopping... DONE");
	}
	
	protected void runTcp() {
		ServerSocket tcpServerSocket = null;

		try {
			tcpServerSocket = new ServerSocket();
			tcpServerSocket.bind(new InetSocketAddress(PORT));
			tcpServerSocket.setSoTimeout(TIMEOUT);
		} catch (IOException e) {
			System.err
					.println("TCP: I/O error occured when openning the socket.");
			e.printStackTrace();
		}

		if (tcpServerSocket != null) {
			System.out.println("TCP: Server waiting for client on port "
					+ tcpServerSocket.getLocalPort() + ".");
			while (currentState == State.ONLINE) {
				try {
					Socket socket = tcpServerSocket.accept();
					System.out.println("TCP: New client (" + socket.getPort()
							+ ") asked for a connection on port "
							+ socket.getLocalPort() + ".");

					threadPool.execute(new TcpRequest(socket));
				} catch (SocketTimeoutException e2) {

				} catch (IOException e) {
					System.err
							.println("TCP: I/O error occured when waiting for a connection.");
					e.printStackTrace();
				}
			}

			System.out.println("TCP: Closing the socket...");
			try {
				tcpServerSocket.close();
				System.out.println("TCP: Closing the socket... DONE");
			} catch (IOException e) {
				System.err
						.println("TCP: I/O error eccoured when closing the socket.");
				e.printStackTrace();
			}
		}
	}
	
	protected void runUdp() {
		DatagramSocket udpServerSocket = null;

		try {
			udpServerSocket = new DatagramSocket(6000);
			udpServerSocket.setSoTimeout(TIMEOUT);
		} catch (SocketException e) {
			System.err.println("UDP: Error occured when openning the socket"
					+ " or binding to specified local port.");
			e.printStackTrace();
		}

		if (udpServerSocket != null) {
			System.out.println("UDP: Server waiting for client on port "
					+ udpServerSocket.getLocalPort() + ".");
			while (currentState == State.ONLINE) {
				try {
					byte[] data = new byte[1024];
					DatagramPacket packet = new DatagramPacket(data,
							data.length);
					udpServerSocket.receive(packet);

					System.out
							.println("UDP: New datagram packet received from "
									+ packet.getSocketAddress().toString()
									+ ".");

					threadPool.execute(new UdpRequest(packet));
				} catch (SocketTimeoutException e2) {

				} catch (IOException e) {
					System.err.println("UDP: I/O error occured.");
					e.printStackTrace();
				}
			}

			System.out.println("UDP: Closing the socket...");
			udpServerSocket.close();
			System.out.println("UDP: Closing the socket... DONE");
		}
	}

	private class TcpRequest implements Runnable {
		private Socket clientSocket;

		public TcpRequest(Socket clientSocket) {
			this.clientSocket = clientSocket;
		}

		public void run() {
			try {
				System.out.println("Received TCP");
				OutputStream out = clientSocket.getOutputStream();
				InputStream in = clientSocket.getInputStream();

				byte[] data = new byte[256];
				in.read(data);

				double receivedMes[] = converter.toDoubleA(data);
				Integer id = new Integer((int) receivedMes[0]);
				System.out.println("Server. Received query from mobile user:"+id);
	
				if(mobileUserMap.containsKey(id)){
					MobileUser mu = mobileUserMap.get(id);
					double currentTime = System.currentTimeMillis();
					double [] pos = new double[2];
					 pos[0] = mu.getCurrentPosition()[0]+mu.getPredictionVector()[0]*((currentTime-mu.getCurrentPosition()[2] )/1000);
					 pos[1] =  mu.getCurrentPosition()[1]+mu.getPredictionVector()[1]*((currentTime-mu.getCurrentPosition()[2] )/1000);
					
					data = new byte[256];
					data = converter.toByta(pos);
					
					out.write(data); // ... and send it back to the client
					out.flush();
					System.out.println("Server. Current position: "+mu.getCurrentPosition()[0]+", "+mu.getCurrentPosition()[1]);
				}
				out.close();
				in.close();
				clientSocket.close();
			} catch (IOException e) {
				System.err.println("TCP: I/O error occured.");
				e.printStackTrace();
			}
		}
	}

	
	private class UdpRequest implements Runnable {
		private DatagramPacket packet;
		private boolean isFirstTimeUdp = true;

		public UdpRequest(DatagramPacket packet) {
			this.packet = packet;
		}

		public void run() {
			System.out.println("Received UDP");
			double allReceivedValues[] =new double[7];
			
			if(firstTimeUPD)
			{ 
				System.out.println("First time UDP");
				allReceivedValues = converter.toDoubleA(packet.getData());
				if(allReceivedValues.length>=4){
					double latitude = allReceivedValues[0];
					double longitude =  allReceivedValues[1];
					double ID =  (int)allReceivedValues[2];
					double time =  allReceivedValues[3];
					predictionVector[0] = 0;
					predictionVector[1] = 0;
					System.out.println("First time UPD. Received values:\nLat:"+latitude+"\nLong:"+longitude+"\nId:"+ID+"\nTime:"+time);
				
				MobileUser mu = new MobileUser(new Integer((int) ID));
				double[] initialPosition = new double[3];
				initialPosition[0] = latitude;
				initialPosition[1] = longitude;
				initialPosition[2] = time;

				mu.setInitialPosition(initialPosition);
				mu.setPredictionVector(predictionVector);
				mobileUserMap.put(new Integer((int) ID), mu);
				System.out.println("New mobile user with ID:"+ID);
				mu.createGPXFile();
				firstTimeUPD = false;
				}
				else{
					System.out.println("No data received via UDp");
				}
			}else{
				if(allReceivedValues.length>=7){
				allReceivedValues = converter.toDoubleA(packet.getData());
				double latitude =  allReceivedValues[0];
				double longitude = allReceivedValues[1];

				double ID =  (int)allReceivedValues[2];
				double time =  allReceivedValues[3];
				predictionVector[0] = allReceivedValues[4];
				predictionVector[1] = allReceivedValues[5];
				double speed = allReceivedValues[6];
				
				System.out.println("UPD. Received values:\nLat:"+latitude+"\nLong:"+longitude+"\nId:"+ID+"\nTime:"+time);
				System.out.println("Prediction vector:["+predictionVector[0]+","+predictionVector[1] +"]\nSpeed:"+speed);
				Integer id = new Integer((int) ID);
				if(mobileUserMap.containsKey(id)){
				MobileUser mu = mobileUserMap.get(id);

				double[] newPosition = new double[3];
				newPosition[0] = latitude;
				newPosition[1] = longitude;
				newPosition[2] = time;		
				mu.setCurrentPosition(newPosition);
				mu.setPredictionVector(predictionVector);

				mu.updateGpxFile();
				if(time==0){
					System.out.println("GOODBYE USER "+id);
					mu.endGPXFile();
				}
				}
				}else{
					System.out.println("No data received via UDp");
				}
			}	
		}
	}	
	


	public static void main(String[] args){
		Server server = new Server();
		server.start();
		try {
			System.in.read();
		} catch (IOException e) {
			e.printStackTrace();
		}
		server.stop();
	}
	
}

